package aggregate

import (
	"context"
	"fmt"
	"gluenet/modules/manager/server/domain/dependency"
	"gluenet/modules/manager/server/domain/entity"
	"gluenet/modules/manager/server/domain/event"
	"gluenet/modules/manager/server/infra"
	"gluenet/modules/manager/server/infra/prepare"
	"path/filepath"
	"strings"
	"time"
)

func init() {
	//fmt.Printf("init config factory\n")
	prepare.RegisterFactory(func() {
		factory := NewInfraTreeFactory()
		prepare.InjectDependency(func() *InfraTreeFactory {
			return factory
		})
	})
}

type InfraTreeFactory struct {
	DetailRepo   dependency.DetailRepo
	AgentRepo    dependency.AgentRepo
	InstanceRepo dependency.InstanceRepo
}

func NewInfraTreeFactory() *InfraTreeFactory {
	factory := &InfraTreeFactory{}
	prepare.FetchDependency(&factory.DetailRepo)
	prepare.FetchDependency(&factory.AgentRepo)
	prepare.FetchDependency(&factory.InstanceRepo)
	return factory
}

func (r *InfraTreeFactory) GetTree() (interface{}, error) {
	return r.DetailRepo.GetByRepoKey(infra.RootValueKey)
}

func (r *InfraTreeFactory) CleanTree() error {
	if err := r.DetailRepo.DeleteKeyWithPrefix(infra.RootKey); err != nil {
		return err
	}
	if err := r.DetailRepo.PutDetailKV(infra.RootKey, map[string]interface{}{}); err != nil {
		return err
	}
	return nil
}

func (r *InfraTreeFactory) BuildTree() error {
	keys, err := r.DetailRepo.GetRepoKeyOnlyWithPrefix(infra.RootKey)
	if err != nil {
		return err
	}
	var tree = entity.InfraTreeNode{}
	var now = time.Now()
	tree.Create = now
	tree.Update = now
	tree.Name = "glue"
	tree.Type = "zone"
	tree.State = "alive"
	tree.Info = nil

	for _, v := range keys {
		//key := filepath.ToSlash(filepath.Dir(v))
		path, err := filepath.Rel(infra.RootKey, v)
		if err != nil {
			return fmt.Errorf("rel %v %v %v", infra.RootKey, v, err)
		}
		r.walkAgent(v, filepath.ToSlash(path), &tree)
	}

	objs, err := r.InstanceRepo.FindAll()
	if err != nil {
		return err
	}

	for _, obj := range objs {
		obj.DetailRepoValue, err = r.DetailRepo.GetByRepoKey(obj.DetailRepoKey)
		if err != nil {
			return err
		}
		obj.RunTimeRepoValue, err = r.DetailRepo.GetByRepoKey(obj.RunTimeRepoKey)
		if err != nil {
			return err
		}

		containMap, err := getInstanceRuntimeAgents(obj.RunTimeRepoValue)
		if err != nil {
			fmt.Printf("\nERROR get instance info %v \n", err)
			continue
		}
		for containName, agentInfo := range containMap {
			leaf, err := walkToLeafAgent(agentInfo[0].(string), agentInfo[1].(string), agentInfo[2].(string), containName, &tree)
			if err != nil {
				fmt.Printf("WARN obj %v name %v info %v \n", obj.GUID, containName, agentInfo)
				continue
			}
			makeApplicationTree(leaf, &obj, containName, agentInfo[3])
			leaf.ChildSize = len(leaf.ChildData)
		}

	}

	tree.ChildSize = len(tree.ChildData)
	if err = r.DetailRepo.PutDetailKV(infra.RootValueKey, tree); err != nil {
		return err
	}
	return nil
}

func makeApplicationTree(node *entity.InfraTreeNode, obj *entity.Instance, containName string, containInfo interface{}) {
	var newNode *entity.InfraTreeNode
	for _, child := range node.ChildData {
		if child.Guid == obj.GUID {
			newNode = child
		}
	}
	if newNode == nil {
		newNode = &entity.InfraTreeNode{}
		newNode.Name = obj.Name
		newNode.Guid = obj.GUID
		newNode.Type = "instance"
		newNode.State = "alive"
		newNode.Info = obj.DetailRepoValue
		newNode.Create = time.Now()
		newNode.Update = time.Now()
		node.ChildData = append(node.ChildData, newNode)
	}

	var childNode = &entity.InfraTreeNode{}
	childNode.Name = containName
	childNode.Guid = obj.GUID
	childNode.Type = "container"
	childNode.State = "alive"
	childNode.Info = containInfo
	childNode.Create = time.Now()
	childNode.Update = time.Now()

	newNode.ChildData = append(newNode.ChildData, childNode)
}

func walkToLeafAgent(platform, guid, machine string, contain string, tree *entity.InfraTreeNode) (*entity.InfraTreeNode, error) {
	switch platform {
	case entity.PlatformKubernetes:
		if tree.Guid == guid && tree.Name == machine && tree.Type == "machine" {
			return tree, nil
		}
	case entity.PlatformGluenets:
		if tree.Guid == guid && tree.Type == "machine" {
			return tree, nil
		}
	default:
		return nil, fmt.Errorf("unknown paltform")
	}

	if tree.Type == "machine" {
		return nil, fmt.Errorf("not this node")
	}

	for _, obj := range tree.ChildData {
		nextNode, err := walkToLeafAgent(platform, guid, machine, contain, obj)
		if err == nil {
			return nextNode, nil
		}
	}
	return nil, fmt.Errorf("unknow error node %v", tree)
}

func getInstanceRuntimeAgents(data interface{}) (map[string][4]interface{}, error) {
	//fmt.Printf("data: %v",data)
	d, ok := data.(map[string]interface{})
	if !ok {
		return nil, fmt.Errorf("data is not map[string]interface")
	}
	agent, ok := d["agents"]
	if !ok {
		return nil, fmt.Errorf("data is not contain agent")
	}
	platform, ok := d["platform"]
	if !ok {
		return nil, fmt.Errorf("data is not contain platform")
	}

	cMap, ok := d["containers"]
	if !ok {
		return nil, fmt.Errorf("data is not contain platform")
	}

	var res = make(map[string][4]interface{})
	switch platform.(string) {
	case entity.PlatformKubernetes:
		containInfo, ok := cMap.(map[string]interface{})
		if !ok {
			return nil, fmt.Errorf("info is not map")
		}

		for contain, containMapi := range containInfo {
			containMap, ok := containMapi.(map[string]interface{})
			if !ok {
				return nil, fmt.Errorf("containers is not map")
			}
			nodeName, ok := containMap["node_name"]
			if !ok {
				return nil, fmt.Errorf("containers not container node name")
			}
			res[contain] = [4]interface{}{platform, agent, nodeName, containInfo[contain]}
		}
		//fmt.Printf("\nres %v\n",res)
	case entity.PlatformGluenets:
		containsMap, ok := agent.(map[string]interface{})
		if !ok {
			return nil, fmt.Errorf("agent is not map")
		}

		containInfo, ok := cMap.(map[string]interface{})
		if !ok {
			return nil, fmt.Errorf("info is not map")
		}

		for contain, guid := range containsMap {
			res[contain] = [4]interface{}{platform, guid, "", containInfo[contain]}
		}
	}

	return res, nil
}

func (r *InfraTreeFactory) walkAgent(gobal string, path string, obj *entity.InfraTreeNode) error {
	paths := strings.Split(path, "/")
	if len(paths) == 0 {
		return nil
	}

	if len(paths) == 1 {
		if paths[0] == "detail" {
			data, err := r.DetailRepo.GetByRepoKey(gobal)
			if err != nil {
				return fmt.Errorf("get detail info %v", err)
			}
			platform, ok := data.(map[string]interface{})["platform"]
			if !ok {
				return fmt.Errorf("can't find platform in agent detailvalue %v", gobal)
			}
			switch platform {
			case entity.PlatformKubernetes:
				name, ok := data.(map[string]interface{})["clusterName"]
				if ok {
					obj.Name = name.(string)
				} else {
					obj.Name = entity.PlatformKubernetes
				}
				obj.Type = "zone"

				nodesI, ok := data.(map[string]interface{})["nodes"]
				if !ok {
					return fmt.Errorf("can't find nodes in agent detailvalue %v", gobal)
				}
				nodes, ok := nodesI.([]interface{})
				if !ok {
					return fmt.Errorf("nodes is not list %v", gobal)
				}

				for _, nodeName := range nodes {
					current := &entity.InfraTreeNode{}
					var now = time.Now()
					current.Create = now
					current.Update = now
					current.Name = nodeName.(string)
					current.Type = "machine"
					current.State = "alive"
					current.Info = nil
					current.Guid = obj.Guid
					obj.ChildData = append(obj.ChildData, current)
				}
			}
		}
		return nil
	}

	var current *entity.InfraTreeNode
	if len(paths) == 2 && paths[1] == "detail" {
		info, err := r.DetailRepo.GetByRepoKey(gobal)
		if err != nil {
			return fmt.Errorf("get detail %v %v", gobal, err)
		}
		agent, err := r.AgentRepo.Get(paths[0])
		if err != nil {
			return fmt.Errorf("get agent %v %v", gobal, err)
		}

		current = &entity.InfraTreeNode{}
		current.Name = agent.HostName
		current.Guid = agent.GUID
		current.Type = "machine"
		current.State = "alive"
		current.Info = info
		current.Create = time.Now()
		current.Update = time.Now()
		obj.ChildData = append(obj.ChildData, current)
	}

	if len(paths) > 2 {
		var match = false
		for _, child := range obj.ChildData {
			if child.Name == paths[0] {
				match = true
				current = child
			}
		}
		if !match {
			current = &entity.InfraTreeNode{}
			current.Name = paths[0]
			current.Guid = ""
			current.Type = "zone"
			current.State = "alive"
			current.Info = nil
			current.Create = time.Now()
			current.Update = time.Now()
			obj.ChildData = append(obj.ChildData, current)
		}
	}

	nextPath, err := filepath.Rel(paths[0], path)
	if err != nil {
		return fmt.Errorf("rel %v %v %v", paths[0], gobal, err)
	}

	if err = r.walkAgent(gobal, filepath.ToSlash(nextPath), current); err != nil {
		return err
	}
	if current != nil {
		current.ChildSize = len(current.ChildData)
	}
	return nil
}

func (r *InfraTreeFactory) reflushTree(obj *event.RepoWatch) error {

	return nil
}

func (r *InfraTreeFactory) Sync(ctx context.Context) {
	ch, cancel := r.DetailRepo.Watch(infra.RootKey)
	for {
		select {
		case <-ctx.Done():
			cancel()
			return
		case data := <-ch:
			r.reflushTree(data)
		}
	}
}
